/**
 *
 * @description: 微信获取token入口文件
 * @author: junyong.hong
 * @createTime: 2018/10/11
 * @version: 1.0.0.0
 * @history:
 *    1、
 *    2、
 *
 */
const fs = require('fs')
const request = require('request-promise')
const { sign } = require('./util')
// 通用的base地址
const base = 'https://api.weixin.qq.com/cgi-bin/'
const mpBase = 'https://mp.weixin.qq.com/cgi-bin/'
// 语义理解（查询特定的语句进行分析）
const semanticUrl = 'https://api.weixin.qq.com/semantic/semproxy/search?'

const api = {
    // 语义理解（查询特定的语句进行分析）
    semanticUrl,
    // 获取token
    accessToken: base + 'token?grant_type=client_credential',
    // 临时素材
    temporary: {
        upload: base + 'media/upload?',
        // 获取临时素材
        fetch: base + 'media/get?'
    },
    // 永久素材
    permanent: {
        // 新增其他类型永久素材
        upload: base + 'material/add_material?',
        // 新增永久图文素材
        uploadNews: base + 'material/add_news?',
        // 上传图文消息内的图片获取url
        uploadNewsPic: base + 'media/uploadimg?',
        // 获取永久素材（可以下载）
        fetch: base + 'material/get_material?',
        // 删除永久素材
        del: base + 'material/del_material?',
        // 更新素材
        update: base + 'material/update_news?',
        // 查看素材的总数
        count: base + 'material/get_materialcount?',
        // 获取素材列表（临时素材不能通过该接口获取）
        batch: base + 'material/batchget_material?'
    },
    // 标签
    tag: {
        // 创建标签
        create: base + 'tags/create?',
        // 获取公众号已创建的标签
        fetch: base + 'tags/get?',
        // 编辑标签
        update: base + 'tags/update?',
        //  删除标签
        del: base + 'tags/delete?',
        // 获取标签下粉丝列表
        fetchUsers: base + 'user/tag/get?',
        // 批量为用户打标签
        batchTag: base + 'tags/members/batchtagging?',
        // 批量为用户取消标签
        batchUnTag: base + 'tags/members/batchuntagging?',
        // 获取用户身上的标签列表
        getUserTags: base + 'tags/getidlist?'
    },
    // 用户
    user: {
        // 获取用户列表
        fetch: base + 'user/get?',
        // 设置用户备注名
        remark: base + 'user/info/updateremark?',
        // 获取用户的详细信息（获取单个）
        info: base + 'user/info?',
        // 获取用户的详细信息（获取批量）
        batch: base + 'user/info/batchget?'
    },
    qrcode: {
        // 创建二维码ticket
        create: base + 'qrcode/create?',
        // 通过ticket换取二维码
        show: mpBase + 'showqrcode?',
    },
    shortUrl: {
        // 长链接转短链接
        create: base + 'shorturl?'
    },
    ai: {
        // AI接口（中文转英文）
        translate: base + 'media/voice/translatecontent?'
    },
    menu: {
        // 创建菜单
        create: base + 'menu/create?',
        // 删除菜单
        del: base + 'menu/delete?',
        // 自定义菜单（千人千面）
        custom: base + 'menu/addconditional?',
        // 获取菜单
        fetch: base + 'menu/get?'
    },
    ticket: {
        // 获取票据
        get: base + 'ticket/getticket?'
    }
}

module.exports = class Wechat {
    constructor (opts) {
        this.opts = Object.assign({}, opts)
        this.appID = opts.appId
        this.appSecret = opts.appSecret
        // 这里的是调用wechat/index.js里的方法
        this.getAccessToken = opts.getAccessToken
        this.saveAccessToken = opts.saveAccessToken
        this.getTicket = opts.getTicket
        this.saveTicket = opts.saveTicket

        // 默认发出获取token请求
        this.fetchAccessToken()
    }

    /**
     * 发送请求的方法
     * @returns {Promise.<void>}
     */
    async request (options) {
        options = Object.assign({}, options, {
            json: true
        })

        try {
            // 借助request-promise发出请求
            const res = await request(options)

            return res
        } catch (err) {
            console.log(err)
        }
    }

    /**
     * 获取access_token异步方法
     *  1、首先检测数据库里的token是否过期
     *  2、过期则刷新
     *  3、token入库
     * @returns {Promise.<void>}
     */
    async fetchAccessToken () {
        let data = await this.getAccessToken()

        // token不合法
        if (!this.isVaild(data, 'access_token')) {
            // 重新获取token
            data = await this.updateAccessToken()
        }

        await this.saveAccessToken(data)

        return data
    }

    /**
     * 更新token异步方法
     * @returns {Promise.<void>}
     */
    async updateAccessToken () {
        const url = `${api.accessToken}&appid=${this.appID}&secret=${this.appSecret}`

        const data = await this.request({ url })
        const now = new Date().getTime()
        // expires_in = 7200秒 = 2个小时
        // 失效时间：时间戳(设置提前20秒失效)
        const expiresIn = now + (data.expires_in - 20 ) * 1000

        data.expires_in = expiresIn

        return data
    }

    /**
     * 获取Ticket
     * @param token
     * @returns {Promise.<void>}
     */
    async fetchTicket (token) {
        let data = await this.getTicket()

        if (!this.isVaild(data, 'ticket')) {
            data = await this.updateTicket(token)
        }

        await this.saveTicket(data)

        return data
    }

    /**
     * 更新ticket
     * @returns {Promise.<void>}
     */
    async updateTicket (token) {
        const url = `${api.ticket.get}access_token=${token}&type=jsapi`

        const data = await this.request({ url })
        const now = new Date().getTime()
        const expiresIn = now + (data.expires_in - 20 ) * 1000

        data.expires_in = expiresIn

        return data
    }

    /**
     * 判断token是否过期
     */
    isVaild (data, name) {
        if (!data || !data[name].expires_in) {
            return false
        }

        const expiresIn = data.expires_in
        const now = new Date().getTime()

        if (now < expiresIn) {
            return true
        } else {
            return false
        }
    }

    /**
     * 上传临时素材
     * @param token
     * @param type 媒体文件类型，分别有图片（image）、语音（voice）、视频（video）和缩略图（thumb，主要用于视频与音乐格式的缩略图）
     * @param material 素材路径
     * @param permanent 标识符（临时还是永久的素材）  默认临时素材
     */
    uploadMaterial (token, type, material, permanent = false) {
        // 以回复4为案例：
        // console.log('type', type)           // image

        let form = {}
        let url = api.temporary.upload

        // 永久素材 form 是个 obj，继承外面传入的新对象
        if (permanent) {
            url = api.permanent.upload
            form = Object.assign(form, permanent)
        }

        // 上传图文消息的图片素材
        if (type === 'pic') {
            url = api.permanent.uploadNewsPic
        }

        // 图文非图文的素材提交表单的切换
        if (type === 'news') {
            url = api.permanent.uploadNews
            form = material
        } else {
            // 读取文件流
            form.media = fs.createReadStream(material)
        }

        let uploadUrl = `${url}access_token=${token}`

        // 根据素材永久性填充 token
        if (!permanent) {   // 临时素材
            uploadUrl += `&type=${type}`
        } else {            // 永久素材
            // 图文
            if (type !== 'news') {
                form.access_token = token
            }
        }

        const options = {
            method: 'POST',
            url: uploadUrl,
            json: true
        }

        // 图文和非图文在 request 提交主体判断
        if (type === 'news') {  // 图文
            options.body = form
        } else {                // 非图文
            options.formData = form
        }

        return options
    }

    /**
     * 封装用来请求接口的入口方法
     * @param operation 要调用的方法名
     * @param args 多个参数
     * @returns {Promise.<void>}
     */
    async handle (operation, ...args) {
        // 获取token
        const tokenData = await this.fetchAccessToken()
        const options = this[operation](tokenData.access_token, ...args)
        const data = await this.request(options)

        return data
    }

    /**
     * 获取永久、临时素材（可以下载）
     * @param token
     * @param mediaId
     * @param type
     * @param permanent
     */
    fetchMaterial (token, mediaId, type, permanent) {
        let form = {}
        // 临时素材
        let fetchUrl = api.temporary.fetch

        // 永久素材
        if (permanent) {
            fetchUrl = api.permanent.fetch
        }

        let url = fetchUrl + 'access_token=' + token
        let options = {
            method: 'POST',
            url
        }

        if (permanent) {
            form.media_id = mediaId
            form.access_token = token
            options.body = form

        } else {
            if (type === 'video') {
                url = url.replace('https:','http:')
            }

            url += '&media_id=' + mediaId
        }

        return options
    }

    /**
     * 删除永久素材
     * @param token
     * @param mediaId
     * @returns {{method: string, url: string, body: {media_id: *}}}
     */
    deleteMaterial (token, mediaId) {
        const form = {
            media_id: mediaId
        }
        const url = `${api.permanent.del}access_token=${token}&media_id=${mediaId}`

        return {
            method: 'POST',
            url,
            body: form
        }
    }

    /**
     * 更新素材
     * @param token
     * @param mediaId
     * @param news
     * @returns {{method: string, url: string, body: {media_id: *}}}
     */
    updateMaterial (token, mediaId, news) {
        let form = {
            media_id: mediaId
        }
        form = Object.assign(form, news)

        const url = `${api.permanent.update}access_token=${token}&media_id=${mediaId}`

        return {
            method: 'POST',
            url,
            body: form
        }
    }

    /**
     * 查看素材的总数
     * @param token
     * @returns {{method: string, url: string}}
     */
    countMaterial (token) {
        const url = `${api.permanent.count}access_token=${token}`

        return {
            method: 'POST',
            url
        }
    }

    /**
     * 获取素材列表（临时素材不能通过该接口获取）
     * @param token
     * @param options
     * @returns {{method: string, url: string, body: *}}
     */
    batchMaterial (token, options) {
        options.type = options.type || 'image'
        options.offset = options.offset || 0
        options.count = options.count || 10

        const url = `${api.permanent.batch}access_token=${token}`

        return {
            method: 'POST',
            url,
            body: options
        }
    }

    /**
     * 创建标签
     * @param token
     * @param name
     * @returns {{method: string, url: string, body: {tag: {name: *}}}}
     */
    createTag (token, name) {
        const body = {
            tag: {
                name
            }
        }

        const url = api.tag.create + 'access_token=' + token

        return {
            method: 'POST',
            url,
            body
        }
    }

    /**
     * 获取公众号已创建的标签
     * @param token
     */
    fetchTags (token) {
        const url = api.tag.fetch + 'access_token=' + token

        return {
            url
        }
    }

    /**
     * 编辑标签
     * @param token
     * @param name
     * @returns {{method: string, url: string, body: {tag: {name: *}}}}
     */
    updateTag (token, id, name) {
        const body = {
            tag: {
                id,
                name
            }
        }

        const url = api.tag.update + 'access_token=' + token

        return {
            method: 'POST',
            url,
            body
        }
    }

    /**
     * 删除标签
     * @param token
     * @param id
     * @param name
     * @returns {{method: string, url: string, body: {tag: {id: *, name: *}}}}
     */
    delTag (token, id) {
        const body = {
            tag: {
                id
            }
        }

        const url = api.tag.del + 'access_token=' + token

        return {
            method: 'POST',
            url,
            body
        }
    }

    /**
     * 获取标签下粉丝列表
     * @param token
     * @param id
     * @param openId 从头开始拉取
     * @returns {{method: string, url: string, body: {tag: {id: *}}}}
     */
    fetchTagUsers (token, id, openId) {
        const body = {
            tagid: id,
            next_openid: openId || ''
        }

        const url = api.tag.fetchUsers + 'access_token=' + token

        return {
            method: 'POST',
            url,
            body
        }
    }

    /**
     * 批量为用户打标签、批量为用户取消标签
     * @param token
     * @param openidList
     * @param id
     * @param unTag true批量为用户打标签  false批量为用户取消标签
     * @returns {{method: string, url: (string|*), body: {openid_list: *, tagid: (*|string)}}}
     */
    batchTag (token, openidList, id, unTag) {
        const body = {
            openid_list: openidList,
            tagid: id || ''
        }

        let url = !unTag ? api.tag.batchTag : api.tag.batchUnTag

        url += 'access_token=' + token

        return {
            method: 'POST',
            url,
            body
        }
    }

    /**
     * 获取用户身上的标签列表
     * @param token
     * @param openId
     */
    getUserTags (token, openId) {
        const body = {
            openid: openId
        }

        const url = api.tag.getUserTags + 'access_token=' + token

        return {
            method: 'POST',
            url,
            body
        }
    }

    /**
     * 设置用户备注名（服务号专用接口）
     * @param token
     * @param openId 用户标识
     * @param remark 新的备注名，长度必须小于30字符
     * @returns {{method: string, url: string, body: {openid: *, remark: *}}}
     */
    remarkUser (token, openId, remark) {
        const body = {
            openid: openId,
            remark
        }

        const url = api.user.remark + 'access_token=' + token

        return {
            method: 'POST',
            url,
            body
        }
    }

    /**
     * 获取用户列表
     * @param token
     * @param openId 拉取列表的最后一个用户的OPENID
     * @returns {{url: (string|string)}}
     */
    fetchUserList (token, openId) {
        const url = api.user.fetch + 'access_token=' + token + '&next_openid=' + (openId || '')

        return {
            url
        }
    }

    /**
     * 获取用户的详细信息
     * @param token
     * @param openId
     * @param lan 语言
     * @returns {{method: string, url: string, body: {openid: *, remark: *}}}
     */
    getUserInfo (token, openId, lan = 'zh_CN') {
        const url = api.user.info + 'access_token=' + token + '&openid=' + openId + '&lang=' + lan

        return {
            url
        }
    }


    /**
     * 批量获取用户详细信息
     * @param token
     * @param openIdList
     * @returns {{method: string, url: string, body: {openid: *, remark: *}}}
     */
    fetchBatchUsers (token, openIdList) {
        const body = {
            user_list: openIdList
        }

        const url = api.user.batch + 'access_token=' + token

        return {
            method: 'POST',
            url,
            body
        }
    }

    sign (ticket, url) {
        return sign(ticket, url)
    }

    /**
     * 创建二维码 ticket
     * @param token
     * @param qr
     */
    createQrcode (token, qr) {
        const url = api.qrcode.create + 'access_token=' + token
        const body = qr

        return {
            method: 'POST',
            url,
            body
        }
    }

    /**
     * 通过ticket换取二维码
     * @param token
     * @param ticket
     */
    showQrcode (ticket) {
        const url = api.qrcode.show + 'ticket=' + encodeURI(ticket)

        return url
    }

    /**
     * 长链接转短链接
     * @param token
     * @param qr
     */
    createShortUrl (token, action = 'long2short', longurl) {
        const url = api.shortUrl.create + 'access_token=' + token

        const body = {
            action,
            long_url: longurl
        }

        return {
            method: 'POST',
            url,
            body
        }
    }

    /**
     * 语义理解（查询特定的语句进行分析）
     * @param token
     * @param qr
     */
    semantic (token, semanticData) {
        const url = api.semanticUrl + 'access_token=' + token

        semanticData.appid = this.appID

        return {
            method: 'POST',
            url,
            body: semanticData
        }
    }

    /**
     * AI接口（中文转英文）
     * @param token
     * @param content
     * @param lfrom 源语言 zh_CH 或 en_US
     * @param lto 目标语言 zh_CH 或 en_US
     */
    aiTranslate (token, body, lfrom, lto) {
        const url = api.ai.translate + 'access_token=' + token + '&lfrom=' + lfrom + '&lto=' + lto

        return {
            method: 'POST',
            url,
            body
        }
    }

    /**
     * 创建菜单
     * @param token
     * @param menu
     * @param rules 自定义菜单（千人千面）
     * @returns {{method: string, url: string, body: *}}
     */
    createMenu (token, menu, rules) {
        let url = api.menu.create + 'access_token=' + token

        if (rules) {
            url = api.menu.custom + 'access_token=' + token
            menu.matchrule = rules
        }

        return {
            method: 'POST',
            url,
            body: menu
        }
    }

    /**
     * 删除菜单
     * @param token
     * @returns {{url: string}}
     */
    deleteMenu (token) {
        const url = api.menu.del + 'access_token=' + token
        return {
            url
        }
    }

    /**
     * 获取菜单
     * @param token
     * @returns {{url: string}}
     */
    fetchMenu (token) {
        const url = api.menu.fetch + 'access_token=' + token
        return {
            url
        }
    }
}